using System;
using System.Windows;
using Columbus.Controls;
using Columbus.Tests.Features.Pages;
using Columbus.Tests.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Columbus.Tests.Features
{
    // ReSharper disable InconsistentNaming
    // BDD style naming
    public class NavigationApplicationFixture
    {
        public class GivenCreatedApplication
        {
            // Application may be:
            // deactivated (we never know forever or just tombstoning), 
            // activated after deactivation, 
            // started normally (after deactivation or just from the scratch)

            [TestClass]
            public class WhenApplicationStartedNormally
            {
                private NavigationAppication application;

                [TestInitialize]
                public void SetUp()
                {
                    application = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithoutLocators();
                    NavigationApplicationExtensions.TransientStoreMock.StorageCleared = false;
                    Background.Execute = action => action();
                    application.StartNormally();
                }

                [TestMethod]
                public void ThenApplicationStorageCleared()
                {
                    Assert.IsTrue(NavigationApplicationExtensions.TransientStoreMock.StorageCleared);
                }

                [TestMethod]
                public void ThenNavigatorInitialized()
                {
                    Assert.IsNotNull(application.GetNavigator().NavigationFrame);
                    Assert.AreEqual(Navigator.DefaultNavigationFrameName, application.GetNavigator().NavigationFrame.Name);
                }

                [TestMethod]
                public void IfPageDoesnNotHaveProperlyNamedFrameThenThrowsException()
                {
                    AssertExtensions.Throws<NavigationFrameNotFoundException>(() => application.StartNormallyWithFramelessShell());
                }

                [TestCleanup]
                public void CleanUp()
                {
                    Background.ResetToDefaults();
                }
            }

            [TestClass]
            public class WhenApplicationDeactivated
            {
                private NavigationAppication application;
                private readonly Func<string, FrameworkElement> viewLocator = s =>
                {
                    Assert.AreEqual(viewName, s);
                    return new EmptyPage();
                };
                private const string viewName = "view1";

                [TestInitialize]
                public void SetUp()
                {
                    application = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithViewLocator(viewLocator);
                    NavigationApplicationExtensions.TransientStoreMock.LastSavedViewState = null;
                    Background.Execute = action => action();
                }

                [TestMethod]
                public void IfNavigatorIsNotInitializedThenThrowsException()
                {
                    AssertExtensions.Throws<NavigationFrameNotFoundException>(() => application.Deactivate());
                }

                [TestMethod]
                public void IfViewModelAttachedToViewThenViewModelDeactivatedAndSaved()
                {
                    var vm = new FixtureViewModel();
                    application.StartNormally();
                    application.GetNavigator().NavigateToView(viewName, vm);
                    application.Deactivate();
                    var savedViewState = NavigationApplicationExtensions.TransientStoreMock.LastSavedViewState;

                    Assert.IsNotNull(savedViewState);
                    Assert.IsTrue(savedViewState.HasViewModel);
                    Assert.IsTrue(vm.IsDeactivated);
                    Assert.AreEqual(viewName, savedViewState.ViewName);
                }

                [TestMethod]
                public void ThenViewNameSaved()
                {
                    application.StartNormally();
                    application.GetNavigator().NavigateToView(viewName);
                    application.Deactivate();
                    var savedViewState = NavigationApplicationExtensions.TransientStoreMock.LastSavedViewState;

                    Assert.IsNotNull(savedViewState);
                    Assert.AreEqual(viewName, savedViewState.ViewName);
                }

                [TestCleanup]
                public void CleanUp()
                {
                    Background.ResetToDefaults();
                }

                public class FixtureViewModel : ViewModel
                {
                    protected override void OnDeactivated(IViewState state)
                    {
                        base.OnDeactivated(state);
                        IsDeactivated = true;
                    }

                    public bool IsDeactivated { get; private set; }
                }
            }

            [TestClass]
            public class WhenApplicationActivated
            {
                private NavigationAppication applicationOne;
                private NavigationAppication applicationTwo;
                private NavigationAppication applicationThree;
                private NavigationAppication applicationFour;
                private const int shellPropertyValue = 777;

                private FixtureViewModelOne viewModelOne;
                private ViewModelSavingConstructorParametersInRandomOrderAndCaseInsensitiveWay viewModelTwo;
                private ViewModelWithDefaultConstructor viewModelThree;
                private ViewModelWithNoEnoughConstructorParamsForActivation viewModelFour;

                private readonly Func<string, FrameworkElement> viewLocator = s =>
                                                                                  {
                                                                                      Assert.AreEqual(viewName, s);
                                                                                      return new EmptyPage();
                                                                                  };
                private const string viewName = "view1";

                [TestInitialize]
                public void SetUp()
                {
                    Background.Execute = action => action();
                    viewModelOne = new FixtureViewModelOne("x", 5);
                    viewModelTwo = new ViewModelSavingConstructorParametersInRandomOrderAndCaseInsensitiveWay("y", 1);
                    viewModelThree = new ViewModelWithDefaultConstructor();
                    viewModelFour = new ViewModelWithNoEnoughConstructorParamsForActivation(8, "z");

                    applicationOne = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithViewLocator(viewLocator);
                    StartAndDeactivateApp(ref applicationOne, viewModelOne);
                    applicationOne.Activate();

                    applicationThree = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithViewLocator(viewLocator);
                    StartAndDeactivateApp(ref applicationThree, viewModelThree);
                    applicationThree.Activate();

                    applicationTwo = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithViewLocator(viewLocator);
                    StartAndDeactivateApp(ref applicationTwo, viewModelTwo);
                    applicationTwo.Activate();

                    applicationFour = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithViewLocator(viewLocator);
                    StartAndDeactivateApp(ref applicationFour, viewModelFour);
                }

                private void StartAndDeactivateApp(ref NavigationAppication app, ViewModel vm)
                {
                    app.StartNormally();
                    app.GetNavigator().NavigateToView(viewName, vm);
                    var shellViewModel = app.InitializedShell.DataContext as OneFrameShellViewModel;
                    Assert.IsNotNull(shellViewModel);
                    // ReSharper disable PossibleNullReferenceException
                    // Value can't be null as I'm asserting it earlier
                    (app.InitializedShell.DataContext as OneFrameShellViewModel).PersistentProperty = shellPropertyValue;
                    // ReSharper restore PossibleNullReferenceException
                    app.Deactivate();
                    app = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithViewLocator(viewLocator);
                }

                [TestMethod]
                public void ThenNavigatorInitialized()
                {
                    Assert.IsNotNull(applicationOne.GetNavigator().NavigationFrame);
                    Assert.AreEqual(Navigator.DefaultNavigationFrameName, applicationOne.GetNavigator().NavigationFrame.Name);
                }

                [TestMethod]
                public void ThenLastViewModelRestoredAndAssignedToLastView()
                {
                    var lastView = GetLastView(applicationOne);
                    var vm = lastView.DataContext as FixtureViewModelOne;

                    Assert.IsNotNull(vm);
                }

                [TestMethod]
                public void ThenShellRestored()
                {
                    Assert.IsNotNull(applicationOne.InitializedShell);
                    var shellViewModel = applicationOne.InitializedShell.DataContext as OneFrameShellViewModel;
                    Assert.IsNotNull(shellViewModel);
                    // ReSharper disable PossibleNullReferenceException
                    // Value can't be null as I'm asserting it earlier
                    Assert.IsTrue(shellViewModel.IsActivated);
                    Assert.AreEqual(shellPropertyValue, shellViewModel.PersistentProperty);
                    // ReSharper restore PossibleNullReferenceException
                }

                [TestMethod]
                public void ThenLastViewLoadedAsFrameContent()
                {
                    var lastView = GetLastView(applicationOne);
                    // ReSharper disable PossibleNullReferenceException
                    // Value can't be null as I'm asserting it earlier
                    Assert.AreEqual(viewName, lastView.Name);
                    // ReSharper restore PossibleNullReferenceException
                }

                [TestMethod]
                public void ThenViewModelWithoutDefaultConstructorActivated()
                {
                    var lastView = GetLastView(applicationOne);
                    var vm = lastView.DataContext as FixtureViewModelOne;
                    Assert.IsNotNull(vm);
                    // ReSharper disable PossibleNullReferenceException
                    // Value can't be null as I'm asserting it earlier
                    Assert.IsTrue(vm.IsActivated);
                    Assert.AreEqual("x", vm.Parameter1);
                    Assert.AreEqual(5, vm.Parameter2);
                    // ReSharper restore PossibleNullReferenceException
                }

                [TestMethod]
                public void ThenViewModelWithDefaultConstructorActivated()
                {
                    var lastView = GetLastView(applicationThree);
                    var vm = lastView.DataContext as ViewModelWithDefaultConstructor;
                    Assert.IsNotNull(vm);
                    // ReSharper disable PossibleNullReferenceException
                    // Value can't be null as I'm asserting it earlier
                    Assert.IsTrue(vm.IsActivated);
                    // ReSharper restore PossibleNullReferenceException
                }


                [TestMethod]
                public void ThenViewModelWithoutDefaultConstructorActivatedInCaseInsensitiveAndParamOrderIndependentManner()
                {
                    var lastView = GetLastView(applicationTwo);
                    var vm = lastView.DataContext as ViewModelSavingConstructorParametersInRandomOrderAndCaseInsensitiveWay;
                    Assert.IsNotNull(vm);
                    // ReSharper disable PossibleNullReferenceException
                    // Value can't be null as I'm asserting it earlier
                    Assert.IsTrue(vm.IsActivated);
                    Assert.AreEqual("y", vm.Parameter1);
                    Assert.AreEqual(1, vm.Parameter2);
                    // ReSharper restore PossibleNullReferenceException
                }

                [TestMethod]
                public void IfNotEnoughConstructionDataForViewModelWithoutDefaultConstructorThenThrowsException()
                {
                    AssertExtensions.Throws<ViewModelRestoringException>(() => applicationFour.Activate());
                }

                [TestCleanup]
                public void CleanUp()
                {
                    Background.ResetToDefaults();
                }

                private static EmptyPage GetLastView(NavigationAppication app)
                {
                    var lastView = GetAppNavigationFrame(app).Content as EmptyPage;
                    Assert.IsNotNull(lastView);
                    return lastView;
                }

                private static Frame GetAppNavigationFrame(NavigationAppication app)
                {
                    return app.GetNavigator().NavigationFrame;
                }

                public class FixtureViewModelOne : ViewModel
                {
                    public string Parameter1 { get; private set; }
                    public int Parameter2 { get; private set; }

                    protected override void OnDeactivated(IViewState state)
                    {
                        state.SaveConstructorParameter("parameter1", Parameter1);
                        state.SaveConstructorParameter("parameter2", Parameter2);
                    }

                    protected override void OnActivated(IViewState state, IApplicationSettings settings)
                    {
                        base.OnActivated(state, settings);
                        IsActivated = true;
                    }

                    public bool IsActivated { get; private set; }

                    public FixtureViewModelOne(string parameter1, int parameter2)
                    {
                        Parameter1 = parameter1;
                        Parameter2 = parameter2;
                    }
                }

                public class ViewModelWithDefaultConstructor : ViewModel
                {
                    protected override void OnActivated(IViewState state, IApplicationSettings settings)
                    {
                        base.OnActivated(state, settings);
                        IsActivated = true;
                    }

                    public bool IsActivated { get; private set; }
                }

                public class ViewModelWithNoEnoughConstructorParamsForActivation : ViewModel
                {
                    private readonly int param1;

                    // ReSharper disable UnusedParameter.Local
                    public ViewModelWithNoEnoughConstructorParamsForActivation(int param1, string param2)
                    // ReSharper restore UnusedParameter.Local
                    {
                        this.param1 = param1;
                    }

                    protected override void OnDeactivated(IViewState state)
                    {
                        base.OnDeactivated(state);
                        state.SaveConstructorParameter("param1", param1);
                    }
                }

                public class ViewModelSavingConstructorParametersInRandomOrderAndCaseInsensitiveWay : ViewModel
                {
                    public string Parameter1 { get; private set; }
                    public int Parameter2 { get; private set; }
                    private readonly string parameter1;
                    private readonly int parameter2;

                    protected override void OnDeactivated(IViewState state)
                    {
                        state.SaveConstructorParameter("paRAmeTEr2", parameter2);
                        state.SaveConstructorParameter("ParaMEter1", parameter1);
                    }

                    // ReSharper disable UnusedParameter.Local
                    public ViewModelSavingConstructorParametersInRandomOrderAndCaseInsensitiveWay(string parameter1, int parameter2, int parameter3)
                    // ReSharper restore UnusedParameter.Local
                    {
                    }

                    public ViewModelSavingConstructorParametersInRandomOrderAndCaseInsensitiveWay(string parameter1, int parameter2)
                    {
                        Parameter1 = parameter1;
                        Parameter2 = parameter2;
                        this.parameter1 = parameter1;
                        this.parameter2 = parameter2;
                    }

                    protected override void OnActivated(IViewState state, IApplicationSettings settings)
                    {
                        base.OnActivated(state, settings);
                        IsActivated = true;
                    }

                    public bool IsActivated { get; private set; }
                }
            }

            [TestClass]
            public class WhenNavigatorRequested
            {
                private NavigationAppication application;
                private readonly INavigator navigator = new Navigator(x => null, x => null);

                [TestInitialize]
                public void SetUp()
                {
                    application = navigator.CreateNewAppWithOneNavigator();
                    application.StartNormally();
                }

                [TestMethod]
                public void ThenDefaultNavigatorReturned()
                {
                    Assert.AreEqual(navigator, application.GetNavigator());
                }

                [TestMethod]
                public void IfRequestedByNameThenNavigatorReturned()
                {
                    Assert.AreEqual(navigator, application.GetNavigator(Navigator.DefaultNavigationFrameName));
                }

                [TestMethod]
                public void IfRequestedByNonExistentFrameNameThenThrowsException()
                {
                    AssertExtensions.Throws<NavigatorNotFoundException>(() => application.GetNavigator("x"));
                }

                [TestMethod]
                public void IfFrameNameIsNotDefaultAndRequestedAsDefaultThenThrowsException()
                {
                    var testApp = NavigationApplicationExtensions.CreateNewAppWithOneNavigatorWithoutLocators("NonDefaultFrameName");
                    testApp.StartNormallyWithNonDefaultFrameName();

                    AssertExtensions.Throws<NavigatorNotFoundException>(() => testApp.GetNavigator());
                }
            }
        }
    }
    // ReSharper restore InconsistentNaming
}